import 'dart:async';
import 'dart:convert';
import 'dart:isolate';

import 'package:im_app/im/socket/dto/base/Resp.dart';
import 'package:im_app/im/socket/router/InitRouter.dart';
import 'package:logger/logger.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

import 'ImWebSocketable.dart';
import 'dto/business/RpcData.dart';

class ImWebSocketIsolate extends ImWebSocketable {

  final String _websocketUrl;
  late Isolate _isolate;
  late SendPort _isolateSendPort;
  var _isStarted = false;

  final _logger = Logger(printer: PrettyPrinter(),);

  ImWebSocketIsolate(this._websocketUrl) {}

  @override
  Future<void> start() async {
    try {
      if (_isStarted) return;
      _isStarted = true;
      ReceivePort receivePort = ReceivePort();
      _isolate = await Isolate.spawn(_doRunner, _IsolateInitData(_websocketUrl, receivePort.sendPort));
      _isolateSendPort = await receivePort.first as SendPort;

      callbacks();
      _logger.i("websocket client start!!!");
    } catch (e){
      _isStarted = false;
    }
  }

  @override
  Future<RpcData> sendData(RpcData rpcData, Function? function, [int? timeout = 60]) async {
    ReceivePort sendDataReceivePort = ReceivePort();
    try {
      assert(_isolateSendPort != null, "_isolateSendPort must be not null");
      _isolateSendPort.send(_IsolateSendData(rpcData: rpcData,
          decodeFunction: function,
          sendPort: sendDataReceivePort.sendPort));
      _logger.i("send message path: ${rpcData.path}");
      RpcData result = await sendDataReceivePort.first.timeout(
          Duration(seconds: timeout?? 60)) as RpcData;
      return result;
    } on TimeoutException catch(e) {
      this.timeout(rpcData);
      _logger.e("send message timeout, path: ${rpcData.path}, data: ${jsonEncode(rpcData.data)}");
       throw e;
    } finally {
      sendDataReceivePort.close();
    }
  }

  @override
  void callbacks() {
    var callbackMap = <String, CallbackData>{};
    var initRouter = InitRouter();
    initRouter.init(callbackMap);

    callbackMap.forEach((path, callbackData){
      callback(path, callbackData.decodeFunction, callbackData.doFunction);
    });
  }


  bool callback(String path, Function decodeFunction, Function doFunction) {
    ReceivePort sendDataReceivePort = ReceivePort();
    assert(_isolateSendPort != null, "_isolateSendPort must be not null");
    _isolateSendPort.send(_IsolateCallBackData(path: path, decodeFunction: decodeFunction, sendPort: sendDataReceivePort.sendPort));
    _logger.i("send message path: $path");
    sendDataReceivePort.listen((message) {
      try {
        if (message is RpcData) {
          var rpcData = message as RpcData;
          doFunction(rpcData.data);
          if (rpcData.extDataMap.containsKey('localSeq')) {
            var okResp = OkResp(coder: 0, successDataTime: DateTime.now(), localSeq: rpcData.extDataMap['localSeq'].toString());
            var rpcDataResp = RpcData(rpcData.path, rpcData.version, 100, rpcData.extDataMap, okResp);
            sendData(rpcDataResp, null);
          }
        }
      } catch(e, stackTrace) {
        _logger.e("websocket 消息处理失败", error: e, stackTrace: stackTrace);
      }
    });
    return true;
  }

  void timeout(RpcData rpcData) {
    _isolateSendPort.send(TimeoutData(rpcData));
  }

  @override
  void close() {
    _isolate?.kill();
  }
}

final _logger = Logger(printer: PrettyPrinter(),);

void _doRunner(_IsolateInitData isloateInitData) async {
  SendPort port = isloateInitData.sendPort;
  String websocketUrl = isloateInitData.webSocketUrl;

  WebSocketChannel? webSocketChannel = null;
  Map<String, dynamic> sendDataMap = {};
  Map<String, dynamic> session = {};

  try {
    if (webSocketChannel == null) {
      webSocketChannel = await websocketListener(websocketUrl, sendDataMap, session, _callBack);
    }
  } catch (error, stackTrace) {
    _logger.i("websocket listener error", error: error, stackTrace: stackTrace);
  }

  ReceivePort receivePort = new ReceivePort();
  port.send(receivePort.sendPort);

  receivePort.listen((dynamic message) async {
    _logger.i("websocket receive send data ${message}");
    var isWebSocketConnected = false;
    if (webSocketChannel == null || webSocketChannel?.closeReason != null) {
      try {
        _logger.i("websocket connection start!!!");
        webSocketChannel = await websocketListener(websocketUrl, sendDataMap, session, _callBack);
        await reconnection(webSocketChannel, sendDataMap, session);
        _logger.i("websocket reconnection end !!!");
        isWebSocketConnected = true;
      } catch(error, stackTrace) {
        _logger.e("websocket listener error", error: error, stackTrace: stackTrace);
        isWebSocketConnected = false ;
      }

      if (!isWebSocketConnected) {
        if (message is _IsolateSendData) {
          _IsolateSendData sendData = message as _IsolateSendData;
          SendPort? sendPort = sendData.sendPort;
          if (sendPort != null) {
            var exceptionResp = ExceptionResp(-100, "网络连接失败");
            RpcData rpcData = RpcData(sendData.rpcData.path, sendData.rpcData.version, 100, sendData.rpcData.extDataMap,
                exceptionResp);
            sendPort.send(rpcData);
          }
        }
        return;
      }
    }
    if (message is SendData) {
      sendMessage(webSocketChannel, sendDataMap, session, message, _noRespCallback);
    } else if (message is CallBackData){
      callBackMessage(sendDataMap, session, message);
    }  else if (message is TimeoutData){
      String? seq = message.rpcData.extDataMap['localSeq'].toString();
      sendDataMap.remove(seq);
    }else {
      _logger.e("不支持这种类型, ${message.runtimeType}");
    }
  });
}

void _noRespCallback(SendData sendData) {
  if (sendData is _IsolateSendData) {
    var isolateSendData = sendData as _IsolateSendData;
    isolateSendData.sendPort?.send(isolateSendData.rpcData);
  }
}

void _callBack(RpcData rpcData, Map<String, dynamic> sendDataMap) {
  String seq = rpcData.extDataMap['localSeq'].toString();
  if (sendDataMap.containsKey(seq)) {
    InnerData innerData = sendDataMap.remove(seq);
    if (innerData != null && innerData.message != null) {
      if (innerData.message is _IsolateSendData) {
        _IsolateSendData sendData = innerData.message as _IsolateSendData;
        SendPort? sendPort = sendData.sendPort;
        if (sendPort != null) {
          sendPort.send(rpcData);
        }
        if (innerData.sendPort != null) {
          innerData.sendPort?.send(rpcData);
        }
      }
    }
  } else if (sendDataMap.containsKey(rpcData.path)) { // callback
    String path = rpcData.path;
    _logger.i("websocket receive server message ${path}");
    _IsolateCallBackData callBackData = sendDataMap[path];
    callBackData.sendPort?.send(rpcData);
  } else {
    _logger.e("websocket no path ${rpcData.path}");
  }
}

class _IsolateInitData {
  final String _webSocketUrl;
  final SendPort _sendPort;

  _IsolateInitData(this._webSocketUrl, this._sendPort);

  String get webSocketUrl => _webSocketUrl;
  SendPort get sendPort => _sendPort;
}

class _IsolateSendData extends SendData {
  SendPort? sendPort;

  _IsolateSendData({required super.rpcData, required super.decodeFunction, this.sendPort});
}

class _IsolateCallBackData extends CallBackData {
  final SendPort sendPort;

  _IsolateCallBackData({required super.path, required super.decodeFunction, required this.sendPort});
}